﻿using System;
using System.Linq.Expressions;
using System.Reflection;

namespace UnitBox {
	public static class Call {

		#region public methods

		public static void Static( Expression<Action> staticCallExpression ) {
			if( staticCallExpression == null )
				throw new ArgumentNullException( "staticCallExpression" );

			// ensure expression is a static call
			var methodCallExpression = staticCallExpression.Body as MethodCallExpression;
			if( methodCallExpression == null || !methodCallExpression.Method.IsStatic )
				throw new ArgumentException( "Expression must be a call to a static method." );

			// invoke the real method if there are no pending calls to intercept
			if( UnitBoxInterceptions.IsLockedDown || !InterceptionQueue.HasPendingInterceptionsTo( methodCallExpression.Method )) {
				staticCallExpression.Compile().Invoke();
			} else { // we know there are pending interceptions to make
				InterceptionBehaviour interceptionBehaviour = InterceptionQueue.GetNextPendingInterception( methodCallExpression );
				if( ! (interceptionBehaviour is ReturnlessInterceptionBehaviour))
					throw new InvalidOperationException( "The interception behaviour has not been properly configured." );
				var returnlessInterceptionBehaviour = (ReturnlessInterceptionBehaviour) interceptionBehaviour;
				returnlessInterceptionBehaviour.Invoke();
			}
		}


		public static TReturnValue Static<TReturnValue>( Expression<Func<TReturnValue>> staticCallExpression ) {
			if( staticCallExpression == null )
				throw new ArgumentNullException( "staticCallExpression" );

			// if expression is a static method call
			if( staticCallExpression.Body is MethodCallExpression) {
				var methodCallExpression = (MethodCallExpression)staticCallExpression.Body;
				MethodInfo method = methodCallExpression.Method;
				if( !method.IsStatic )
					throw new ArgumentException( "Expression must be a call to a static method." );

				// invoke the real method if there are no pending calls to intercept
				if( UnitBoxInterceptions.IsLockedDown || !InterceptionQueue.HasPendingInterceptionsTo( method )) {
					return staticCallExpression.Compile().Invoke();
				} else { // we know there are pending interceptions to make
					InterceptionBehaviour interceptionBehaviour = InterceptionQueue.GetNextPendingInterception( methodCallExpression );
					if( !(interceptionBehaviour is ValueReturningInterceptionBehaviour<TReturnValue>) )
						throw new InvalidOperationException( "The interception behaviour has not been properly configured." );

					var valueReturningInterceptionBehaviour = (ValueReturningInterceptionBehaviour<TReturnValue>) interceptionBehaviour;
					return valueReturningInterceptionBehaviour.Invoke();
				}

			// if expression is a static property getter
			} else if( staticCallExpression.Body is MemberExpression ) {
				var memberExpression = (MemberExpression)staticCallExpression.Body;
				MemberTypes memberType = memberExpression.Member.MemberType;
				if( memberType != MemberTypes.Property )
					throw new ArgumentException( "Expression must be a call to a static property." );

				var property = (PropertyInfo) memberExpression.Member;
				MethodInfo getterMethod = property.GetGetMethod();
				if( !getterMethod.IsStatic )
					throw new ArgumentException( "Expression must be a call to a static property." );

				// invoke the real method if there are no pending calls to intercept
				if( UnitBoxInterceptions.IsLockedDown || !InterceptionQueue.HasPendingInterceptionsTo( getterMethod )) {
					return staticCallExpression.Compile().Invoke();
				} else { // we know there are pending interceptions to make
					InterceptionBehaviour interceptionBehaviour = InterceptionQueue.GetNextPendingInterception( memberExpression );
					if( !(interceptionBehaviour is ValueReturningInterceptionBehaviour<TReturnValue>) )
						throw new InvalidOperationException( "The interception behaviour has not been properly configured." );

					var valueReturningInterceptionBehaviour = (ValueReturningInterceptionBehaviour<TReturnValue>) interceptionBehaviour;
					return valueReturningInterceptionBehaviour.Invoke();
				}
			}

			throw new ArgumentException( "Expression must be a call to a static method or property." );
		}


		public static void StaticSetter<TProperty>( Func<TProperty> setterExpression, TProperty value ) {
//			throw new NotImplementedException();
		}

		#endregion

	}
}
